package ae.pcfc.etks.online.ced.web.web.util;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Comparator;

public final class DynamicComparator implements Comparator, Serializable {

	private String method;

	private boolean sortAsc;

	private boolean stringAsNumber = false;

	private boolean primitive = false;

	public static final int EQUAL = 0;

	public static final int LESS_THAN = -1;

	public static final int GREATER_THAN = 1;

	public DynamicComparator(boolean sortAsc) {
		this(sortAsc, false);
	}

	public DynamicComparator(boolean sortAsc, boolean stringAsNumber) {
		this.sortAsc = sortAsc;
		this.stringAsNumber = stringAsNumber;
		this.primitive = true;
	}

	public DynamicComparator(String field, boolean sortAsc) {
		this(field, sortAsc, false);
	}

	public DynamicComparator(String field, boolean sortAsc,
			boolean stringAsNumber) {
		this.method = constructMethodName(field);
		this.sortAsc = sortAsc;
		this.stringAsNumber = stringAsNumber;
		this.primitive = false;
	}

	private final static String constructMethodName(String name) {
		StringBuffer fieldName = new StringBuffer("get");
		fieldName.append(name.substring(0, 1).toUpperCase());
		fieldName.append(name.substring(1));
		return fieldName.toString();
	}

	public int compare(Object o1, Object o2) {
		boolean val1Null = false;
		boolean val2Null = false;

		try {

			if (primitive)
				return primitiveCompare(o1, o2);

			// determine the return type
			Method method = getMethod(o1);
			final String returnType = method.getReturnType().getName();

			if (returnType.equals("java.lang.String")) {
				final String f1 = (String) invoke(method, o1);
				method = getMethod(o2);
				final String f2 = (String) invoke(method, o2);

				if (stringAsNumber) {
					Double f1Parsed = (f1 == null) ? null : Double
							.parseDouble(f1);
					Double f2Parsed = (f2 == null) ? null : Double
							.parseDouble(f2);

					if (f1Parsed != null && f2Parsed != null) {
						int retType = 0;
						if (f1Parsed == f2Parsed)
							retType = EQUAL;
						if (f1Parsed < f2Parsed)
							retType = LESS_THAN;
						if (f1Parsed > f2Parsed)
							retType = GREATER_THAN;
						return retType * getSortOrder();
					}

					if (f1Parsed == null && f2Parsed != null) {
						return LESS_THAN * getSortOrder();
					}

					if (f1Parsed != null && f2Parsed == null) {
						return GREATER_THAN * getSortOrder();
					}
				} else {
					if (f1 != null && f2 != null) {
						return f1.compareTo(f2) * getSortOrder();
					}

					if (f1 == null && f2 != null) {
						return LESS_THAN * getSortOrder();
					}

					if (f1 != null && f2 == null) {
						return GREATER_THAN * getSortOrder();
					}
				}
				if (f1 == null && f2 == null) {
					return EQUAL * getSortOrder();
				}
			}

			if (returnType.equals("int")) {
				int f1 = 0;
				try {
					f1 = ((Integer) invoke(method, o1)).intValue();
				} catch (NullPointerException npe) {
					val1Null = true;
				}

				method = getMethod(o2);

				int f2 = 0;
				try {
					f2 = ((Integer) invoke(method, o2)).intValue();
				} catch (NullPointerException npe) {
					val2Null = true;
				}

				if (!val1Null && !val2Null) {
					int retType = 0;
					if (f1 == f2)
						retType = EQUAL;
					if (f1 < f2)
						retType = LESS_THAN;
					if (f1 > f2)
						retType = GREATER_THAN;
					return retType * getSortOrder();
				}

				if (val1Null && !val2Null) {
					return LESS_THAN * getSortOrder();
				}

				if (!val1Null && val2Null) {
					return GREATER_THAN * getSortOrder();
				}

				if (val1Null && val2Null) {
					return EQUAL * getSortOrder();
				}
			}

			if (returnType.equals("double")) {
				double f1 = 0;
				try {
					f1 = ((Double) invoke(method, o1)).doubleValue();
				} catch (NullPointerException npe) {
					val1Null = true;
				}

				method = getMethod(o2);

				double f2 = 0;
				try {
					f2 = ((Double) invoke(method, o2)).doubleValue();
				} catch (NullPointerException npe) {
					val2Null = true;
				}

				if (!val1Null && !val2Null) {
					int retType = 0;
					if (f1 == f2)
						retType = EQUAL;
					if (f1 < f2)
						retType = LESS_THAN;
					if (f1 > f2)
						retType = GREATER_THAN;
					return retType * getSortOrder();
				}

				if (val1Null && !val2Null) {
					return LESS_THAN * getSortOrder();
				}

				if (!val1Null && val2Null) {
					return GREATER_THAN * getSortOrder();
				}

				if (val1Null && val2Null) {
					return EQUAL * getSortOrder();
				}
			}

			throw new RuntimeException(
					"DynamicComparator does not currently support '"
							+ returnType + "'!");
		} catch (NumberFormatException nfe) {
			System.out.println("Error " + nfe);
			return LESS_THAN;
		} catch (NoSuchMethodException nsme) {
			System.out.println("Error " + nsme);
			return LESS_THAN;
		} catch (IllegalAccessException iae) {
			System.out.println("Error " + iae);
			return LESS_THAN;
		} catch (InvocationTargetException ite) {
			System.out.println("Error " + ite);
			return LESS_THAN;
		}
	}

	private int primitiveCompare(Object o1, Object o2) {
		if (stringAsNumber) {
			Double f1Parsed = (o1 == null) ? null : Double.parseDouble(o1
					.toString());
			Double f2Parsed = (o2 == null) ? null : Double.parseDouble(o2
					.toString());

			if (f1Parsed != null && f2Parsed != null) {
				int retType = 0;
				if (f1Parsed == f2Parsed)
					retType = DynamicComparator.EQUAL;
				if (f1Parsed < f2Parsed)
					retType = DynamicComparator.LESS_THAN;
				if (f1Parsed > f2Parsed)
					retType = DynamicComparator.GREATER_THAN;
				return retType * getSortOrder();
			}

			if (f1Parsed == null && f2Parsed != null) {
				return LESS_THAN * getSortOrder();
			}

			if (f1Parsed != null && f2Parsed == null) {
				return GREATER_THAN * getSortOrder();
			}
		} else {
			if (o1 != null && o2 != null) {

				Double od1 = Double.valueOf(o1.toString());
				Double od2 = Double.valueOf(o2.toString());

				int retType = 0;
				if (od1 == od2)
					retType = DynamicComparator.EQUAL;
				if (od1 < od2)
					retType = DynamicComparator.LESS_THAN;
				if (od1 > od2)
					retType = DynamicComparator.GREATER_THAN;
				return retType * getSortOrder();
			}

			if (o1 == null && o2 != null) {
				return LESS_THAN * getSortOrder();
			}

			if (o1 != null && o2 == null) {
				return GREATER_THAN * getSortOrder();
			}
		}
		if (o1 == null && o2 == null) {
			return EQUAL * getSortOrder();
		}
		return LESS_THAN;
	}

	/**
	 * Not used for sorting. Only here to meet the requirements of the
	 * Comparator interface.
	 * 
	 * @param o
	 *            The object for comparison
	 * @return boolean
	 */
	public boolean equals(Object o) {
		return true;
	}

	private final Method getMethod(Object o) throws NoSuchMethodException {
		return o.getClass().getMethod(method, null);
	}

	private final static Object invoke(Method method, Object o)
			throws InvocationTargetException, IllegalAccessException {
		return method.invoke(o, null);
	}

	/**
	 * 
	 * @return -1 to change the sort order if appropriate.
	 */
	private int getSortOrder() {
		return sortAsc ? 1 : -1;
	}

}
